using System;
using System.ComponentModel;
using System.Windows.Data;

namespace FluentSilverlight.Expressions.TextItems
{
    public class BindingInterceptor : INotifyPropertyChanged
    {
        private readonly object model;
        private readonly string modelProperty;
        private readonly IValueDependencyObject valueDependencyObject;

        public event PropertyChangedEventHandler PropertyChanged;
        public event EventHandler<ModelChangedEventArgs> ModelValueChanged;
        public event EventHandler<ViewValueChangedEventArgs> ViewValueChanged;

        public BindingInterceptor(object model, string modelProperty, IValueDependencyObject valueDependencyObject, IDependencyPropertyBinder dependencyPropertyBinder)
        {
            this.model = model;
            this.modelProperty = modelProperty;

            this.valueDependencyObject = valueDependencyObject;
            this.valueDependencyObject.ViewValueChanged += OnViewValueChanged;

            var notifyPropChg = model as INotifyPropertyChanged;

            if (notifyPropChg != null)
                notifyPropChg.PropertyChanged += ModelPropertyChanged;

            var binding = new Binding("Value") { Source = this, Mode = BindingMode.TwoWay };
            dependencyPropertyBinder.SetBinding(binding);
        }

        public void SetModelValue(object value)
        {
            var prop = model.GetType().GetProperty(modelProperty);
            var oldValue = prop.GetValue(model, null);

            if ((oldValue == null && value == null) || (oldValue != null && oldValue.Equals(value))) return;

            var dest = ConvertTo(value, prop.PropertyType);
            prop.SetValue(model, dest, null);
        }

        private static object ConvertTo(object value, Type targetType)
        {
            var text = value == null ? "" : value.ToString();

            if (targetType == typeof(string))
                return text;

            if (targetType == typeof(bool))
                return bool.Parse(text);
            if (targetType == typeof(bool?))
                return string.IsNullOrEmpty(text) ? (bool?)null : bool.Parse(text);

            if (targetType == typeof(int))
                return int.Parse(text);
            if (targetType == typeof(int?))
                return string.IsNullOrEmpty(text) ? (int?)null : int.Parse(text);

            if (targetType == typeof(float))
                return float.Parse(text);
            if (targetType == typeof(float?))
                return string.IsNullOrEmpty(text) ? (float?)null : float.Parse(text);

            if (targetType == typeof(double))
                return double.Parse(text);
            if (targetType == typeof(double?))
                return string.IsNullOrEmpty(text) ? (double?)null : double.Parse(text);

            if (targetType == typeof(decimal))
                return decimal.Parse(text);
            if (targetType == typeof(decimal?))
                return string.IsNullOrEmpty(text) ? (decimal?)null : decimal.Parse(text);

            if (targetType == typeof(DateTime))
                return DateTime.Parse(text);
            if (targetType == typeof(DateTime?))
                return string.IsNullOrEmpty(text) ? (DateTime?)null : DateTime.Parse(text);

            if (targetType == typeof(Guid))
                return new Guid(text);
            if (targetType == typeof(Guid?))
                return string.IsNullOrEmpty(text) ? (Guid?)null : new Guid(text);

            throw new ArgumentException("Unexpected type received. Cannot convert.");
        }

        public void SetViewValue(object value)
        {
            Value = value;
        }

        public object Value
        {
            get { return valueDependencyObject.GetValue(); }
            set
            {
                var oldValue = valueDependencyObject.GetValue();

                if (oldValue == value) return;

                valueDependencyObject.SetValue(value);

                if (PropertyChanged != null)
                    PropertyChanged(this, new PropertyChangedEventArgs("Value"));
            }
        }

        private void OnViewValueChanged(object sender, ViewValueChangedEventArgs e)
        {
            if (ViewValueChanged != null)
                ViewValueChanged(this, new ViewValueChangedEventArgs(e.Value));
        }

        void ModelPropertyChanged(object sender, PropertyChangedEventArgs e)
        {
            if (e.PropertyName != modelProperty) return;

            var modelValue = model.GetType().GetProperty(modelProperty).GetValue(model, null);

            if (ModelValueChanged != null)
                ModelValueChanged(this, new ModelChangedEventArgs(modelValue));
        }
    }
}